﻿using System;
using System.Collections.Generic;
using iTextSharp.text.pdf;
using PDFPatcher.Common;
using PDFPatcher.Model;
using Matrix = iTextSharp.text.pdf.parser.Matrix;

namespace PDFPatcher.Processor
{
	sealed class PdfPageCommandProcessor : PdfContentStreamProcessor, IPdfPageCommandContainer
	{

		public bool HasCommand => Commands.Count > 0;
		/// <summary>
		/// 分析内容后得到的 PDF 命令操作符及操作数列表。
		/// </summary>
		public IList<PdfPageCommand> Commands { get; }
		readonly Stack<EnclosingCommand> _commandStack;
		EnclosingCommand _currentCommand;
		float _textWidth;

		public PdfPageCommandProcessor() {
			PopulateOperators();
			RegisterContentOperator("TJ", new AccumulatedShowTextArray());
			Commands = new List<PdfPageCommand>();
			_commandStack = new Stack<EnclosingCommand>();
		}

		public PdfPageCommandProcessor(PRStream form)
			: this() {
			var resources = form.Locate<PdfDictionary>(PdfName.RESOURCES);
			ProcessContent(PdfReader.GetStreamBytes(form), resources);
		}

		protected override void DisplayPdfString(PdfString str) {
			var gs = CurrentGraphicState;
			var font = gs.Font;
			if (font == null) {
				return;
			}
			float totalWidth = 0;
			foreach (var c in font.DecodeText(str)) {
				var w = font.GetWidth(c) / 1000.0f;
				var wordSpacing = (c == ' ' ? gs.WordSpacing : 0f);
				totalWidth += (w * gs.FontSize + gs.CharacterSpacing + wordSpacing) * gs.HorizontalScaling;
			}

			_textWidth = totalWidth;
		}

		protected override void InvokeOperator(PdfLiteral oper, List<PdfObject> operands) {
			base.InvokeOperator(oper, operands);
			PdfPageCommand cmd;
			var o = oper.ToString();
			switch (o) {
				case "TJ":
					cmd = new PaceAndTextCommand(oper, operands, GetTextInfo(new PdfString()), CurrentGraphicState.Font);
					break;
				case "Tj":
				case "'":
				case "\"":
					cmd = new TextCommand(oper, operands, GetTextInfo(operands[0] as PdfString));
					break;
				case "Tf":
					cmd = new FontCommand(oper, operands, CurrentGraphicState.Font.FontName);
					break;
				case "cm":
				case "Tm":
					cmd = new MatrixCommand(oper, operands);
					break;
				case "BI":
					cmd = new InlineImageCommand(oper, operands);
					break;
				default:
					cmd = EnclosingCommand.IsStartingCommand(o) ? new EnclosingCommand(oper, operands) : new PdfPageCommand(oper, operands);
					break;
			}

			if (EnclosingCommand.IsEndingCommand(o)) {
				_commandStack.Pop();
				_currentCommand = _commandStack.Count > 0 ? _commandStack.Peek() : null;
				return;
			}
			if (_currentCommand != null) {
				_currentCommand.Commands.Add(cmd);
			}
			else {
				Commands.Add(cmd);
			}
			if (cmd is EnclosingCommand ec) {
				_commandStack.Push(ec);
				_currentCommand = ec;
			}
		}

		/// <summary>
		/// 将 <see cref="Operands"/> 的内容写入到目标 <see cref="System.IO.Stream"/>。
		/// </summary>
		/// <param name="target">目标流对象。</param>
		internal void WritePdfCommands(System.IO.Stream target) {
			foreach (var item in Commands) {
				item.WriteToPdf(target);
			}
		}

		/// <summary>
		/// 将 <see cref="Operands"/> 的内容写入到目标 <paramref name="pdf"/> 的第 <paramref name="pageNumber"/> 页。
		/// </summary>
		/// <param name="pdf">目标 <see cref="PdfReader"/>。</param>
		/// <param name="pageNumber">要写入的页码。</param>
		internal void WritePdfCommands(PdfReader pdf, int pageNumber) {
			using (var ms = new System.IO.MemoryStream()) {
				WritePdfCommands(ms);
				ms.Flush();
				pdf.SafeSetPageContent(pageNumber, ms.ToArray());
			}
		}

		internal void WritePdfCommands(PageProcessorContext context) {
			WritePdfCommands(context.Pdf, context.PageNumber);
		}

		private static string GetOperandsTextValue(List<PdfObject> operands) {
			var n = operands.ConvertAll((po) => po.Type == PdfObject.NUMBER ? ValueHelper.ToText(((PdfNumber)po).DoubleValue) : null);
			n.RemoveAt(n.Count - 1);
			return String.Join(" ", n.ToArray());
		}

		private TextInfo GetTextInfo(PdfString text) {
			var gs = CurrentGraphicState;
			var m = TextMatrix;
			return new TextInfo {
				PdfString = text,
				Text = gs.Font != null ? gs.Font.DecodeText(text) : String.Empty,
				Size = gs.FontSize * m[Matrix.I11],
				Region = new Bound(m[Matrix.I31], m[Matrix.I32], m[Matrix.I31] + _textWidth, 0),
				Font = gs.Font
			};
		}
	}
}
